package com.sh.data.engine.domain.common.service;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.sh.data.engine.domain.common.model.domain.JoinRelationship;
import com.sh.data.engine.domain.common.model.domain.Relationship;
import com.sh.data.engine.domain.common.model.domain.RelationshipElement;
import com.sh.data.engine.domain.common.model.domain.Table;
import com.sh.data.engine.domain.common.model.domain.*;
import gudusoft.gsqlparser.EDbVendor;
import gudusoft.gsqlparser.dlineage.DataFlowAnalyzer;
import gudusoft.gsqlparser.dlineage.IDataFlowAnalyzer;
import gudusoft.gsqlparser.dlineage.dataflow.listener.DataFlowHandleListener;
import gudusoft.gsqlparser.dlineage.dataflow.model.Process;
import gudusoft.gsqlparser.dlineage.dataflow.model.*;
import gudusoft.gsqlparser.dlineage.dataflow.model.json.Coordinate;
import gudusoft.gsqlparser.dlineage.dataflow.model.xml.*;
import gudusoft.gsqlparser.dlineage.metadata.MetadataUtil;
import gudusoft.gsqlparser.dlineage.metadata.Transform;
import gudusoft.gsqlparser.dlineage.util.Pair;
import gudusoft.gsqlparser.dlineage.util.Pair3;
import gudusoft.gsqlparser.dlineage.util.RemoveDataflowFunction;
import gudusoft.gsqlparser.sqlenv.ESQLDataObjectType;
import gudusoft.gsqlparser.sqlenv.TSQLEnv;
import gudusoft.gsqlparser.util.SQLUtil;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.springframework.util.StringUtils;
import y.base.DataProvider;
import y.base.Graph;
import y.base.Node;
import y.io.GraphMLIOHandler;
import y.layout.LayoutMultiplexer;
import y.layout.Layouter;
import y.layout.PortCandidate;
import y.layout.grouping.RecursiveGroupLayouter;
import y.layout.hierarchic.IncrementalHierarchicLayouter;
import y.view.*;
import y.view.hierarchy.GroupLayoutConfigurator;
import y.view.hierarchy.HierarchyManager;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

public class SqlflowService {

    public String getTableName(table t, EDbVendor vendor) {
        if (SubType.dblink.name().equals(t.getSubType())) {
            return t.getDatabase() + "." + SQLUtil.getIdentifierNormalTableName(vendor,
                t.getName().substring(0, t.getName().lastIndexOf("@")).trim());
        }
        return SQLUtil.getIdentifierNormalTableName(vendor, t.getName());
    }

    public Map<String, Set<relationship>> getColumnRelationMap(EDbVendor vendor, dataflow dataFlow,
                                                               Set<relationship> relationships) {
        Map<String, Set<relationship>> columnRelationMap = new LinkedHashMap<>();
        if (relationships == null) {
            return columnRelationMap;
        }
        relationships.stream().forEach(t -> {
            if (t.getTarget() == null || t.getSources() == null || t.getSources().isEmpty()) {
                return;
            }
            String columnName = SQLUtil.getIdentifierNormalColumnName(vendor, t.getTarget().getColumn());
            columnRelationMap.putIfAbsent(columnName, new LinkedHashSet());
            ((Set<relationship>) columnRelationMap.get(columnName)).add(t);
            //  t.getSources().forEach(());
        });
        return columnRelationMap;
    }

    public Map<String, Set<relationship>> getTableRelationMap(EDbVendor vendor, dataflow dataFlow,
                                                              Set<relationship> relationships) {
        Map<String, Set<relationship>> tableRelationMap = new LinkedHashMap<>();
        if (relationships == null) {
            return tableRelationMap;
        }
        Map<String, String> tableIdMap = new HashMap<>();
        if (dataFlow.getTables() != null) {
            for (table t : dataFlow.getTables()) {
                tableIdMap.put(t.getId(), getTableName(t, vendor));
            }
        }
        if (dataFlow.getViews() != null) {
            for (table t : dataFlow.getViews()) {
                tableIdMap.put(t.getId(), getTableName(t, vendor));
            }
        }
        relationships.stream().forEach(t -> {
            if (t.getTarget() == null || t.getSources() == null || t.getSources().isEmpty()) {
                return;
            }
            String targetParentId = t.getTarget().getParent_id();
            String tableName = (String) tableIdMap.get(targetParentId);
            tableRelationMap.putIfAbsent(tableName, new LinkedHashSet());
            ((Set<relationship>) tableRelationMap.get(tableName)).add(t);
            //  t.getSources().forEach(());
        });
        return tableRelationMap;
    }

    public Map<String, Set<relationship>> getSchemaRelationMap(EDbVendor vendor, dataflow dataFlow,
                                                               Set<relationship> relationships) {
        Map<String, Set<relationship>> schemaRelationMap = new LinkedHashMap<>();
        if (relationships == null) {
            return schemaRelationMap;
        }
        Map<String, String> schemaIdMap = new HashMap<>();
        if (dataFlow.getTables() != null) {
            if (TSQLEnv.supportSchema(vendor)) {
                for (table t : dataFlow.getTables()) {
                    schemaIdMap.put(t.getId(), t.getSchema());
                }
            } else {
                for (table t : dataFlow.getTables()) {
                    schemaIdMap.put(t.getId(), t.getDatabase());
                }
            }
        }
        if (dataFlow.getViews() != null) {
            if (TSQLEnv.supportSchema(vendor)) {
                for (table t : dataFlow.getViews()) {
                    schemaIdMap.put(t.getId(), t.getSchema());
                }
            } else {
                for (table t : dataFlow.getViews()) {
                    schemaIdMap.put(t.getId(), t.getDatabase());
                }
            }
        }
        relationships.stream().forEach(t -> {
            if (t.getTarget() == null || t.getSources() == null || t.getSources().isEmpty()) {
                return;
            }
            String targetParentId = t.getTarget().getParent_id();
            String schemaName = SQLUtil.getIdentifierNormalName(vendor,
                (String) schemaIdMap.get(targetParentId), ESQLDataObjectType.dotSchema);
            schemaRelationMap.putIfAbsent(schemaName, new LinkedHashSet());
            ((Set<relationship>) schemaRelationMap.get(schemaName)).add(t);
            //   t.getSources().forEach(());
        });
        relationships.stream().forEach(t -> {
            if (t.getCaller() == null || t.getCallees() == null || t.getCallees().isEmpty()) {
                return;
            }
            String targetParentId = t.getCaller().getParent_id();
            String schemaName = SQLUtil.getIdentifierNormalName(vendor,
                (String) schemaIdMap.get(targetParentId), ESQLDataObjectType.dotSchema);
            schemaRelationMap.putIfAbsent(schemaName, new LinkedHashSet());
            ((Set<relationship>) schemaRelationMap.get(schemaName)).add(t);
            //   t.getCallees().forEach(());
        });
        return schemaRelationMap;
    }

    public Map<String, Set<relationship>> getDataBaseRelationMap(EDbVendor vendor,
                                                                 dataflow dataFlow) {
        Map<String, Set<relationship>> databaseRelationMap = new LinkedHashMap<>();
        if (dataFlow.getRelationships() == null) {
            return databaseRelationMap;
        }
        Map<String, String> databaseIdMap = new HashMap<>();
        if (dataFlow.getTables() != null) {
            for (table t : dataFlow.getTables()) {
                databaseIdMap.put(t.getId(), t.getDatabase());
            }
        }
        if (dataFlow.getViews() != null) {
            for (table t : dataFlow.getViews()) {
                databaseIdMap.put(t.getId(), t.getDatabase());
            }
        }
        if (dataFlow.getRelationships() != null) {
            dataFlow.getRelationships().stream().forEach(t -> {
                if (t.getTarget() == null || t.getSources() == null || t.getSources().isEmpty()) {
                    return;
                }
                String targetParentId = t.getTarget().getParent_id();
                String databaseName = SQLUtil.getIdentifierNormalName(vendor,
                    (String) databaseIdMap.get(targetParentId), ESQLDataObjectType.dotCatalog);
                databaseRelationMap.putIfAbsent(databaseName, new LinkedHashSet());
                ((Set<relationship>) databaseRelationMap.get(databaseName)).add(t);
                //   t.getSources().forEach(());
            });
            dataFlow.getRelationships().stream().forEach(t -> {
                if (t.getCaller() == null || t.getCallees() == null || t.getCallees().isEmpty()) {
                    return;
                }
                String targetParentId = t.getCaller().getParent_id();
                String databaseName = SQLUtil.getIdentifierNormalName(vendor,
                    (String) databaseIdMap.get(targetParentId), ESQLDataObjectType.dotCatalog);
                databaseRelationMap.putIfAbsent(databaseName, new LinkedHashSet());
                ((Set<relationship>) databaseRelationMap.get(databaseName)).add(t);
                // t.getCallees().forEach(());
            });
        }
        return databaseRelationMap;
    }

    public JSONObject getSummaryInfo(EDbVendor vendor, dataflow model, String databaseArg,
                                     String schemaArg, String tableArg, String columnArg) {
        long time = System.currentTimeMillis();
        JSONObject summary = new JSONObject();
        Set<String> processes = new HashSet<>();
        Set<String> databases = new HashSet<>();
        Set<String> schemas = new HashSet<>();
        Set<String> tables = new HashSet<>();
        Set<String> views = new HashSet<>();
        Set<String> columns = new HashSet<>();
        String database = SQLUtil.getIdentifierNormalName(vendor, databaseArg,
            ESQLDataObjectType.dotCatalog);
        String schema = SQLUtil.getIdentifierNormalName(vendor, schemaArg,
            ESQLDataObjectType.dotSchema);
        String table = SQLUtil.getIdentifierNormalName(vendor, tableArg, ESQLDataObjectType.dotTable);
        String column = SQLUtil.getIdentifierNormalName(vendor, columnArg,
            ESQLDataObjectType.dotColumn);
        if (model.getProcesses() != null) {
            model.getProcesses().stream().forEach(t -> {
                if (t.getDatabase() != null) {
                    if (database == null) {
                        databases.add(SQLUtil.getIdentifierNormalName(vendor, t.getDatabase(),
                            ESQLDataObjectType.dotCatalog));
                    } else if (!SQLUtil.getIdentifierNormalName(vendor, t.getDatabase(),
                        ESQLDataObjectType.dotCatalog).equals(database)) {
                        return;
                    }
                }
                if (t.getSchema() != null) {
                    if (schema == null) {
                        schemas.add(SQLUtil.getIdentifierNormalName(vendor, t.getDatabase(),
                            ESQLDataObjectType.dotCatalog) + ":" + SQLUtil.getIdentifierNormalName(vendor,
                            t.getSchema(), ESQLDataObjectType.dotSchema));
                    } else if (!SQLUtil.getIdentifierNormalName(vendor, t.getSchema(),
                        ESQLDataObjectType.dotSchema).equals(schema)) {
                        return;
                    }
                }
                processes.add(t.getQueryHashId());
            });
        }
        if (model.getTables() != null) {
            model.getTables().stream().forEach(t -> {
                if (t.getDatabase() != null) {
                    if (database == null) {
                        databases.add(SQLUtil.getIdentifierNormalName(vendor, t.getDatabase(),
                            ESQLDataObjectType.dotCatalog));
                    } else if (!SQLUtil.getIdentifierNormalName(vendor, t.getDatabase(),
                        ESQLDataObjectType.dotCatalog).equals(database)) {
                        return;
                    }
                }
                if (t.getSchema() != null) {
                    if (schema == null) {
                        schemas.add(SQLUtil.getIdentifierNormalName(vendor, t.getDatabase(),
                            ESQLDataObjectType.dotCatalog) + ":" + SQLUtil.getIdentifierNormalName(vendor,
                            t.getSchema(), ESQLDataObjectType.dotSchema));
                    } else if (!SQLUtil.getIdentifierNormalName(vendor, t.getSchema(),
                        ESQLDataObjectType.dotSchema).equals(schema)) {
                        return;
                    }
                }
//        if (str1 == null || str1.equals(
//            SQLUtil.getIdentifierNormalName(vendor, t.getName(), ESQLDataObjectType.dotTable))) {
//          if (str1 == null) {
//            tables.add(SQLUtil.getIdentifierNormalName(vendor, t.getDatabase(),
//                ESQLDataObjectType.dotCatalog) + ":" + SQLUtil.getIdentifierNormalName(vendor,
//                t.getSchema(), ESQLDataObjectType.dotSchema) + ":"
//                + SQLUtil.getIdentifierNormalName(vendor, t.getName(),
//                ESQLDataObjectType.dotTable));
//          }
//          if (t.getColumns() != null) {
////            t.getColumns().stream().forEach(());
//          }
//        }
            });
        }
        if (model.getViews() != null) {
            model.getViews().stream().forEach(t -> {
                if (t.getDatabase() != null) {
                    if (database == null) {
                        databases.add(SQLUtil.getIdentifierNormalName(vendor, t.getDatabase(),
                            ESQLDataObjectType.dotCatalog));
                    } else if (!SQLUtil.getIdentifierNormalName(vendor, t.getDatabase(),
                        ESQLDataObjectType.dotCatalog).equals(database)) {
                        return;
                    }
                }
                if (t.getSchema() != null) {
                    if (schema == null) {
                        schemas.add(SQLUtil.getIdentifierNormalName(vendor, t.getDatabase(),
                            ESQLDataObjectType.dotCatalog) + ":" + SQLUtil.getIdentifierNormalName(vendor,
                            t.getSchema(), ESQLDataObjectType.dotSchema));
                    } else if (!SQLUtil.getIdentifierNormalName(vendor, t.getSchema(),
                        ESQLDataObjectType.dotSchema).equals(schema)) {
                        return;
                    }
                }
//        if (str1 == null || str1.equals(
//            SQLUtil.getIdentifierNormalName(vendor, t.getName(), ESQLDataObjectType.dotTable))) {
//          if (str1 == null) {
//            views.add(SQLUtil.getIdentifierNormalName(vendor, t.getDatabase(),
//                ESQLDataObjectType.dotCatalog) + ":" + SQLUtil.getIdentifierNormalName(vendor,
//                t.getSchema(), ESQLDataObjectType.dotSchema) + ":"
//                + SQLUtil.getIdentifierNormalName(vendor, t.getName(),
//                ESQLDataObjectType.dotTable));
//          }
//          if (t.getColumns() != null) {
//            t.getColumns().stream().forEach(());
//          }
//        }
            });
        }
        Map<String, Set<relationship>> databaseRelationMap = getDataBaseRelationMap(vendor, model);
        summary.put("process", Integer.valueOf(processes.size()));
        if (database == null) {
            summary.put("database", Integer.valueOf((databases == null) ? 0 : databases.size()));
        } else {
            summary.put("database", Integer.valueOf(0));
        }
        if (schema == null) {
            summary.put("schema", Integer.valueOf((schemas == null) ? 0 : schemas.size()));
        } else {
            summary.put("schema", Integer.valueOf(0));
        }
        if (table == null) {
            summary.put("table", Integer.valueOf((tables == null) ? 0 : tables.size()));
            summary.put("view", Integer.valueOf((views == null) ? 0 : views.size()));
        } else {
            summary.put("table", Integer.valueOf(0));
            summary.put("view", Integer.valueOf(0));
        }
        if (column == null) {
            summary.put("column", Integer.valueOf((columns == null) ? 0 : columns.size()));
        } else {
            summary.put("column", Integer.valueOf(0));
        }
        if (database == null) {
            summary.put("relationship", Integer.valueOf(
                (model.getRelationships() == null) ? 0 : model.getRelationships().size()));
        } else if (databaseRelationMap != null && databaseRelationMap.containsKey(database)) {
            if (schema == null) {
                summary.put("relationship", Integer.valueOf((databaseRelationMap.get(database) == null) ? 0
                    : ((Set) databaseRelationMap.get(database)).size()));
            } else {
                Map<String, Set<relationship>> schemaRelationMap = getSchemaRelationMap(vendor, model,
                    databaseRelationMap.get(database));
                if (schemaRelationMap != null && schemaRelationMap.containsKey(schema)) {
                    if (table == null) {
                        summary.put("relationship", Integer.valueOf((schemaRelationMap.get(schema) == null) ? 0
                            : ((Set) schemaRelationMap.get(schema)).size()));
                    } else {
                        Map<String, Set<relationship>> tableRelationMap = getTableRelationMap(vendor, model,
                            schemaRelationMap.get(schema));
                        if (tableRelationMap != null && tableRelationMap.containsKey(table)) {
                            if (column == null) {
                                summary.put("relationship", Integer.valueOf(
                                    (tableRelationMap.get(table) == null) ? 0
                                        : ((Set) tableRelationMap.get(table)).size()));
                            } else {
                                Map<String, Set<relationship>> columnRelationMap = getColumnRelationMap(vendor,
                                    model, tableRelationMap.get(table));
                                if (columnRelationMap != null && columnRelationMap.containsKey(column)) {
                                    summary.put("relationship", Integer.valueOf(
                                        (columnRelationMap.get(column) == null) ? 0
                                            : ((Set) columnRelationMap.get(column)).size()));
                                }
                            }
                        }
                    }
                }
            }
        }
        if (!summary.containsKey("relationship")) {
            summary.put("relationship", Integer.valueOf(0));
        }
        table[] mostRelationTableArray = getMostRelationTables(model);
        JSONArray mostRelationTables = new JSONArray();
        for (int i = 0; i < mostRelationTableArray.length; i++) {
            JSONObject object = new JSONObject();
            table relationshipTable = mostRelationTableArray[i];
            object.put("database",
                SQLUtil.getIdentifierNormalName(vendor, relationshipTable.getDatabase(),
                    ESQLDataObjectType.dotCatalog));
            object.put("schema", SQLUtil.getIdentifierNormalName(vendor, relationshipTable.getSchema(),
                ESQLDataObjectType.dotSchema));
            object.put("table",
                SQLUtil.getIdentifierNormalName(vendor, getColumnName(relationshipTable.getName()),
                    ESQLDataObjectType.dotTable));
            mostRelationTables.add(object);
        }
        summary.put("mostRelationTables", mostRelationTables);
        return summary;
    }

    private String getColumnName(String tableName) {
        if (tableName.endsWith("]")) {
            String[] arrayOfString = tableName.split("\\.\\[");
            String str = arrayOfString[arrayOfString.length - 1].replace("]", "");
            return str;
        }
        if (tableName.endsWith("\"")) {
            String[] arrayOfString = tableName.split("\\.\"");
            String str = arrayOfString[arrayOfString.length - 1].replace("\"", "");
            return str;
        }
        if (tableName.endsWith("'")) {
            String[] arrayOfString = tableName.split("\\.'");
            String str = arrayOfString[arrayOfString.length - 1].replace("'", "");
            return str;
        }
        if (tableName.endsWith("`")) {
            String[] arrayOfString = tableName.split("\\.`");
            String str = arrayOfString[arrayOfString.length - 1].replace("`", "");
            return str;
        }
        String[] split = tableName.split("\\.");
        String name = split[split.length - 1];
        return name;
    }

    private table[] getMostRelationTables(dataflow dataFlow) {
        if (dataFlow.getRelationships() != null) {
            Map<String, AtomicInteger> tableRelationCountMap = new HashMap<>();
            dataFlow.getRelationships().stream().forEach(t -> {
                if (t.getTarget() == null || t.getSources() == null || t.getSources().isEmpty()) {
                    return;
                }
                String targetParentId = t.getTarget().getParent_id();
                if (targetParentId == null) {
                    targetParentId = t.getTarget().getId();
                }
                tableRelationCountMap.putIfAbsent(targetParentId, new AtomicInteger());
                ((AtomicInteger) tableRelationCountMap.get(targetParentId)).incrementAndGet();
//        t.getSources().forEach(());
            });
            Map<String, table> databaseIdMap = new HashMap<>();
            if (dataFlow.getTables() != null) {
                for (table t : dataFlow.getTables()) {
                    databaseIdMap.put(t.getId(), t);
                }
            }
            if (dataFlow.getViews() != null) {
                for (table t : dataFlow.getViews()) {
                    databaseIdMap.put(t.getId(), t);
                }
            }
            TreeMap<Integer, TreeSet<String>> relationshipCountMap = new TreeMap<>();
            for (Map.Entry<String, AtomicInteger> value : tableRelationCountMap.entrySet()) {
                if (databaseIdMap.containsKey(value.getKey())) {
                    relationshipCountMap.putIfAbsent(
                        Integer.valueOf(((AtomicInteger) value.getValue()).get()), new TreeSet<>());
                    ((TreeSet) relationshipCountMap.get(
                        Integer.valueOf(((AtomicInteger) value.getValue()).get()))).add(value.getKey());
                }
            }
            List<table> tables = new ArrayList<>();
            Iterator<Integer> keyIter = relationshipCountMap.descendingKeySet().iterator();
            boolean flag = false;
            while (keyIter.hasNext()) {
                TreeSet<String> ids = relationshipCountMap.get(keyIter.next());
                for (String id : ids) {
                    if (databaseIdMap.containsKey(id)) {
                        tables.add(databaseIdMap.get(id));
                        if (tables.size() >= 3) {
                            flag = true;
                            break;
                        }
                    }
                }
                if (flag) {
                    break;
                }
            }
            return tables.<table>toArray(new table[0]);
        }
        return new table[0];
    }

    public JSONObject generateSqlflowGraphByDataFlowWithCache(dataflow originDataflow,
                                                              dataflow dataflow1, String dbvendor, String showRelationshipType, boolean showLinkOnly,
                                                              boolean hideColumn, boolean ignoreFunction, boolean containGraphML,
                                                              boolean normalizeIdentifier, String jobId, String serverName, String databaseName,
                                                              String schemaName, String tableName) {
        JSONObject result = new JSONObject();
        JSONObject data = new JSONObject();
        List<RelationshipType> relationshipTypes = (List<RelationshipType>) Arrays.<String>asList(
                showRelationshipType.split(",")).stream().map(t -> RelationshipType.valueOf(t.trim()))
            .collect(Collectors.toList());
        try {
            Pair3<JSONObject, dataflow, Set<String>> graph = getSqlflowGraph(EDbVendor.valueOf(dbvendor),
                relationshipTypes, serverName, databaseName, schemaName, tableName, showLinkOnly,
                hideColumn, ignoreFunction, dataflow1, containGraphML);
            if (containGraphML) {
                data.put("graphML", ((JSONObject) graph.first).remove("graphML"));
            }
            if (ignoreFunction) {
                DataFlow model = getSqlflowModel(EDbVendor.valueOf(dbvendor), (dataflow) graph.second,
                    normalizeIdentifier, false);
                data.put("sqlflow", model);
            } else {
                DataFlow model = getSqlflowModel(EDbVendor.valueOf(dbvendor), originDataflow,
                    normalizeIdentifier, false);
                data.put("sqlflow", model);
            }
            data.put("dbObjIds", graph.third);
            data.put("graph", graph.first);
            result.put("code", Integer.valueOf(200));
        } catch (Exception e) {
            result.put("code", Integer.valueOf(500));
            result.put("error", "Generate sqlflow graph failed: " + e.getMessage());
        }
        result.put("data", data);
        if (!StringUtils.isEmpty(jobId)) {
            result.put("jobId", jobId);
        }
        return result;
    }

    public Pair<dataflow, String> generateDataflowWithCache(TSQLEnv sqlEnv, SqlInfo[] sqltexts,
                                                            String dbvendor, String showRelationshipType, boolean simpleOutput, boolean ignoreRecordSet,
                                                            boolean treatArgumentsInCountFunctionAsDirectDataflow, boolean showConstantTable,
                                                            boolean showTransform, String sqlHash, boolean parallel, LogRecord logRecord,
                                                            boolean normalizeIdentifier, String defaultServer, String defaultDatabase,
                                                            String defaultSchema) {
        DataFlowAnalyzer dataFlowAnalyzer = null;
        if (sqltexts.length > 10) {
            parallel = true;
        }
        List<RelationshipType> relationshipTypes = (List<RelationshipType>) Arrays.<String>asList(
                showRelationshipType.split(",")).stream().map(t -> RelationshipType.valueOf(t.trim()))
            .collect(Collectors.toList());
        IDataFlowAnalyzer dataFlow = null;
        StringBuffer errorMessage = new StringBuffer();
        try {

            dataFlowAnalyzer = new DataFlowAnalyzer(sqltexts, EDbVendor.valueOf(dbvendor),
                (simpleOutput && relationshipTypes.contains(RelationshipType.fdd)
                    && relationshipTypes.size() == 1));

            dataFlowAnalyzer.getOption().setDefaultServer(defaultServer);
            dataFlowAnalyzer.getOption().setDefaultDatabase(defaultDatabase);
            dataFlowAnalyzer.getOption().setDefaultSchema(defaultSchema);
            dataFlowAnalyzer.setLinkOrphanColumnToFirstTable(true);
            dataFlowAnalyzer.setSqlEnv(sqlEnv);
            dataFlowAnalyzer.setShowJoin(relationshipTypes.contains(RelationshipType.join));
            dataFlowAnalyzer.setIgnoreRecordSet(ignoreRecordSet);
            dataFlowAnalyzer.setHandleListener((DataFlowHandleListener) logRecord);
            dataFlowAnalyzer.setShowCountTableColumn(treatArgumentsInCountFunctionAsDirectDataflow);
            dataFlowAnalyzer.setShowConstantTable(showConstantTable);
            dataFlowAnalyzer.setTransform(showTransform);
            if (showTransform) {
                dataFlowAnalyzer.setTransformCoordinate(true);
            }
            dataFlowAnalyzer.getOption()
                .setHandleListener((DataFlowHandleListener) new DataFlowHandleTimeListener());
            dataFlowAnalyzer.getOption().setOutput(false);
            dataFlowAnalyzer.getOption().setShowERDiagram(true);
            dataFlowAnalyzer.generateDataFlow(true);
            if (dataFlowAnalyzer.getErrorMessages() != null && !dataFlowAnalyzer.getErrorMessages()
                .isEmpty()) {
                for (int i = 0; i < dataFlowAnalyzer.getErrorMessages().size(); i++) {
                    errorMessage.append(
                            ((ErrorInfo) dataFlowAnalyzer.getErrorMessages().get(i)).getErrorMessage())
                        .append("\r\n");
                }
            }
//      File dataFlowSqlInfo = new File("../data/session/sqlflow/" + String.format("%02d",
//          new Object[]{Integer.valueOf(Math.abs(sqlHash.hashCode()) % 100)}) + "/" + sqlHash
//          + ".dataflow_sqlinfo.json");
//      SQLUtil.saveFile(dataFlowAnalyzer.generateSqlInfos(), dataFlowSqlInfo);
        } catch (Exception e) {
            errorMessage.append(e.getMessage()).append("\r\n");
        } finally {
            if (dataFlowAnalyzer != null) {
                dataFlowAnalyzer.dispose();
            }
        }
        return new Pair(dataFlowAnalyzer.getDataFlow(), errorMessage.toString().trim());
    }

    public static DataFlow getSqlflowModel(EDbVendor vendor, dataflow dataflow1,
                                           boolean normalizeIdentifier, boolean summary) {
        return getSqlflowModel(vendor, dataflow1, false, normalizeIdentifier, summary);
    }

    public static DataFlow getSqlflowModel(EDbVendor vendor, dataflow dataflow1,
                                           boolean filterCoordinate, boolean normalizeIdentifier, boolean summary) {
        DataFlow model = new DataFlow();
        model.setDbobjs(MetadataUtil.convertDataflowToMetadata(vendor, dataflow1));
        List<Relationship> relationships = new ArrayList<>();
        if (dataflow1.getRelationships() != null && !summary) {
            for (relationship relationship : dataflow1.getRelationships()) {
                Relationship relationshipModel = null;
                if (relationship.getType().equals("join")) {
                    JoinRelationship joinRelationship = new JoinRelationship();
                    joinRelationship.setCondition(relationship.getCondition());
                    joinRelationship.setJoinType(relationship.getJoinType());
                    joinRelationship.setClause(relationship.getClause());
                } else {
                    relationshipModel = new Relationship();
                }
                relationshipModel.setId(relationship.getId());
                relationshipModel.setProcessId(relationship.getProcessId());
                relationshipModel.setType(relationship.getType());
                relationshipModel.setEffectType(relationship.getEffectType());
                relationshipModel.setPartition(relationship.getPartition());
                relationshipModel.setFunction(relationship.getFunction());
                relationshipModel.setTimestampMin(relationship.getTimestampMin());
                relationshipModel.setTimestampMax(relationship.getTimestampMax());
                if (relationship.getTarget() != null && relationship.getSources() != null
                    && !relationship.getSources().isEmpty()) {
                    RelationshipElement targetModel = new RelationshipElement();
                    targetColumn target = relationship.getTarget();
                    if (normalizeIdentifier) {
                        targetModel.setColumn(
                            SQLUtil.getIdentifierNormalColumnName(vendor,
                                target.getColumn()));
                        targetModel.setParentName(
                            SQLUtil.getIdentifierNormalTableName(vendor,
                                target.getParent_name()));
                    } else {
                        targetModel.setColumn(target.getColumn());
                        targetModel.setParentName(target.getParent_name());
                    }
                    targetModel.setId(target.getId());
                    targetModel.setParentId(target.getParent_id());
                    targetModel.setCoordinates(Coordinate.parse(target.getCoordinate()));
                    targetModel.setFunction(target.getFunction());
                    relationshipModel.setTarget(targetModel);
                    List<RelationshipElement> sourceModels = new ArrayList<>();
                    for (sourceColumn source : relationship.getSources()) {
                        RelationshipElement sourceModel = new RelationshipElement();
                        if (normalizeIdentifier) {
                            sourceModel.setColumn(
                                SQLUtil.getIdentifierNormalColumnName(vendor,
                                    source.getColumn()));
                            sourceModel.setParentName(
                                SQLUtil.getIdentifierNormalTableName(vendor,
                                    source.getParent_name()));
                            sourceModel.setSourceName(
                                SQLUtil.getIdentifierNormalColumnName(vendor,
                                    source.getSource_name()));
                        } else {
                            sourceModel.setColumn(source.getColumn());
                            sourceModel.setParentName(source.getParent_name());
                            sourceModel.setSourceName(source.getSource_name());
                        }
                        sourceModel.setColumnType(source.getColumn_type());
                        sourceModel.setId(source.getId());
                        sourceModel.setParentId(source.getParent_id());
                        sourceModel.setSourceId(source.getSource_id());
                        sourceModel.setCoordinates(Coordinate.parse(source.getCoordinate()));
                        sourceModel.setClauseType(source.getClauseType());
                        sourceModels.add(sourceModel);
                        if (source.getTransforms() != null && !source.getTransforms().isEmpty()) {
                            List<Transform> transforms = new ArrayList<>();
                            for (gudusoft.gsqlparser.dlineage.dataflow.model.xml.transform transform : source.getTransforms()) {
                                Transform item = new Transform();
                                item.setCode(transform.getCode());
                                item.setType(transform.getType());
//                item.setCoordinates(Coordinate.parse(transform.getCoordinate(true)));
                                transforms.add(item);
                            }
                            sourceModel.setTransforms(transforms.<Transform>toArray(new Transform[0]));
                        }
                    }
                    relationshipModel.setSources(
                        sourceModels.<RelationshipElement>toArray(new RelationshipElement[0]));
                    relationships.add(relationshipModel);
                    continue;
                }
                if (relationship.getCaller() != null && relationship.getCallees() != null
                    && !relationship.getCallees().isEmpty()) {
                    RelationshipElement targetModel = new RelationshipElement();
                    targetColumn target = relationship.getCaller();
                    if (normalizeIdentifier) {
                        targetModel.setName(
                            SQLUtil.getIdentifierNormalColumnName(vendor,
                                target.getName()));
                    } else {
                        targetModel.setName(target.getName());
                    }
                    targetModel.setId(target.getId());
                    targetModel.setCoordinates(Coordinate.parse(target.getCoordinate()));
                    targetModel.setType(target.getType());
                    relationshipModel.setCaller(targetModel);
                    List<RelationshipElement> sourceModels = new ArrayList<>();
                    for (sourceColumn source : relationship.getCallees()) {
                        RelationshipElement sourceModel = new RelationshipElement();
                        if (normalizeIdentifier) {
                            sourceModel.setName(SQLUtil.getIdentifierNormalColumnName(vendor, source.getName()));
                        } else {
                            sourceModel.setName(source.getName());
                        }
                        sourceModel.setId(source.getId());
                        sourceModel.setCoordinates(Coordinate.parse(source.getCoordinate()));
                        sourceModel.setType(source.getType());
                        sourceModels.add(sourceModel);
                    }
                    relationshipModel.setCallees(sourceModels
                        .<RelationshipElement>toArray(new RelationshipElement[0]));
                    relationships.add(relationshipModel);
                }
            }
        }
//    if (dataflow1.getErrors() != null && !dataflow1.getErrors().isEmpty()) {
//      List<Error> errorModels = new ArrayList<>();
//      for (int i = 0; i < dataflow1.getErrors().size(); i++) {
//        Error errorModel = new Error();
//        error error = dataflow1.getErrors().get(i);
//        errorModel.setErrorMessage(error.getErrorMessage());
//        errorModel.setErrorType(error.getErrorType());
//        errorModel.setCoordinates(Coordinate.parse(error.getCoordinate()));
//        errorModels.add(errorModel);
//      }
//      model.setErrors(errorModels.<Error>toArray(new Error[0]));
//    }
        List<Process> processes = new ArrayList<>();
        if (dataflow1.getProcesses() != null) {
//      for (process process : dataflow1.getProcesses()) {
//        Process processModel = new Process();
//        processModel.setId(process.getId());
//        processModel.setName(process.getName());
//        processModel.setProcedureId(process.getProcedureId());
//        processModel.setProcedureName(process.getProcedureName());
//        processModel.setType(process.getType());
//        processModel.setCoordinates(Coordinate.parse(process.getCoordinate()));
//        processModel.setQueryHashId(process.getQueryHashId());
//        if (process.getTransforms() != null && !process.getTransforms().isEmpty()) {
//          List<Transform> transforms = new ArrayList<>();
//          for (transform transform : process.getTransforms()) {
//            Transform item = new Transform();
//            item.setCode(transform.getCode());
//            item.setType(transform.getType());
//            item.setCoordinates(Coordinate.parse(transform.getCoordinate(true)));
//            transforms.add(item);
//          }
//          processModel.setTransforms(transforms
//              .<Transform>toArray(new Transform[0]));
//        }
//        processes.add(processModel);
//      }
        }
        model.setProcesses(processes.<Process>toArray(new Process[0]));
        model.setRelationships(relationships.<Relationship>toArray(new Relationship[0]));
        return model;
    }

    private void layoutGraph(final Graph2DView view, final boolean isShowJoin, final String orientation) {
        final RowLayouter rowLayouter = new RowLayouter();
        final IncrementalHierarchicLayouter ihl = new IncrementalHierarchicLayouter();
        if (!StringUtils.isEmpty((Object) orientation)) {
            final String upperCase = orientation.toUpperCase();
            switch (upperCase) {
                case "TOP_TO_BOTTOM": {
                    ihl.setLayoutOrientation((byte) 0);
                    break;
                }
                case "RIGHT_TO_LEFT": {
                    ihl.setLayoutOrientation((byte) 3);
                    break;
                }
                case "BOTTOM_TO_TOP": {
                    ihl.setLayoutOrientation((byte) 2);
                    break;
                }
                default: {
                    ihl.setLayoutOrientation((byte) 1);
                    break;
                }
            }
        } else {
            ihl.setLayoutOrientation((byte) 1);
        }
        ihl.setOrthogonallyRouted(true);
        ihl.setMinimumLayerDistance(50.0);
        if (isShowJoin) {
            ihl.setMinimumLayerDistance(250.0);
        }
        view.getGraph2D().addDataProvider(RecursiveGroupLayouter.GROUP_NODE_LAYOUTER_DPKEY,
            new DataProvider() {
                @Override
                public Object get(Object o) {
                    return null;
                }

                @Override
                public int getInt(Object o) {
                    return 0;
                }

                @Override
                public double getDouble(Object o) {
                    return 0;
                }

                @Override
                public boolean getBool(Object o) {
                    return false;
                }
            });
        final GroupLayoutConfigurator glc = new GroupLayoutConfigurator(view.getGraph2D());
        try {
            glc.prepareAll();
            final RecursiveGroupLayouter rgl = new RecursiveGroupLayouter((Layouter) ihl);
            new Graph2DLayoutExecutor().doLayout(view, (Layouter) rgl);
        } finally {
            glc.restoreAll();
            view.getGraph2D().removeDataProvider(PortCandidate.SOURCE_PCLIST_DPKEY);
            view.getGraph2D().removeDataProvider(PortCandidate.TARGET_PCLIST_DPKEY);
            view.getGraph2D().removeDataProvider(LayoutMultiplexer.LAYOUTER_DPKEY);
        }
        view.updateView();
        view.fitContent();
    }

    private String getGraphML(Graph2DView view) throws IOException {
        StringWriter output = new StringWriter();
        (new GraphMLIOHandler()).write(view.getGraph2D(), output);
        output.close();
        return output.toString();
    }

    private void appendTableGraph(List<Table> tables, Document document, String id) {
        Table table = new Table();
        table.setId(id);
        Element geometry = (Element) document.select("geometry").get(0);
        String width = geometry.attr("width");
        String height = geometry.attr("height");
        String x = geometry.attr("x");
        String y = geometry.attr("y");
        table.setX(Float.parseFloat(x));
        table.setY(Float.parseFloat(y));
        table.setWidth(Float.parseFloat(width));
        table.setHeight(Float.parseFloat(height));
        Element labelElement = (Element) document.select("NodeLabel").get(0);
        width = labelElement.attr("width");
        height = labelElement.attr("height");
        x = labelElement.attr("x");
        y = labelElement.attr("y");
        String content = labelElement.text();
        String fontFamily = labelElement.attr("fontFamily");
        String fontSize = labelElement.attr("fontSize");
        Label label = new Label();
        label.setX(Float.parseFloat(x));
        label.setY(Float.parseFloat(y));
        label.setWidth(Float.parseFloat(width));
        label.setHeight(Float.parseFloat(height));
        label.setFontFamily(fontFamily);
        label.setFontSize(fontSize);
        label.setContent(content);
        table.setLabel(label);
        tables.add(table);
    }

    private Pair3<JSONObject, dataflow, Set<String>> getSqlflowGraph(EDbVendor vendor, List<RelationshipType> relationshipTypes, String serverName, String databaseName, String schemaName, String tableName, boolean showLinkOnly, boolean hideColumn, boolean ignoreFunction, dataflow dataflow1, boolean containGraphML) throws Exception {
        String graphml;
        JSONObject graph = new JSONObject();
        DataFlowGraph dataFlowGraph = new DataFlowGraph();
        Map<String, String> tooltipMap = new HashMap<>();
        String orientation = dataflow1.getOrientation();
        if (ignoreFunction && !relationshipTypes.contains(RelationshipType.er))
            dataflow1 = (new RemoveDataflowFunction()).removeFunction(dataflow1, vendor);
        Pair3<String, Boolean, Set<String>> graphContent = dataFlowGraph.generateDataFlowGraph(vendor, dataflow1, showLinkOnly, hideColumn, relationshipTypes, serverName, databaseName, schemaName, tableName, tooltipMap);
        List<Object> graphNodes = dataFlowGraph.getNodes();
        List<relationship> relationships = dataFlowGraph.getRelationships();
        if (graphContent != null && graphContent.first != null) {
            graphml = ((String) graphContent.first).replace("\r\n", "\n").replace("\n", " ");
        } else {
            graphml = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?><graphml xmlns=\"http://graphml.graphdrawing.org/xmlns\" xmlns:java=\"http://www.yworks.com/xml/yfiles-model/1.0/java\" xmlns:sys=\"http://www.yworks.com/xml/yfiles-model/markup/primitives/2.0\" xmlns:x=\"http://www.yworks.com/xml/yfiles-model/markup/2.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:y=\"http://www.yworks.com/xml/graphml\" xsi:schemaLocation=\"http://graphml.graphdrawing.org/xmlns http://www.yworks.com/xml/schema/graphml/1.1/ygraphml.xsd\"></graphml>";
        }
        Graph2DView view = initGraph(graphml);
        layoutGraph(view, relationshipTypes.contains(RelationshipType.join), orientation);
        String graphML = getGraphML(view);
        String xml = graphML.replace("y:", "");
        int groupNum = -1;
        int columnNum = -1;
        int index = 0;
        Map<String, String[]> listIdMap = (Map) new LinkedHashMap<>();
        Map<String, String> modelColumnIdMap = new LinkedHashMap<>();
        Map<String, String> modelTableIdMap = new LinkedHashMap<>();
        Map<String, String[]> relationshipIdMap = (Map) new LinkedHashMap<>();
        LinkedList<Table> tables = new LinkedList<>();
        List<Item> items = new ArrayList<>();
        Pattern nodePattern = Pattern.compile("<node\\s+id=\"(.+?)\".+?(<data.+?</data>)", 32);
        Matcher nodeMatcher = nodePattern.matcher(xml);
        Map<String, Document> nodeMap = new HashMap<>();
        while (nodeMatcher.find())
            nodeMap.put(nodeMatcher.group(1), Jsoup.parse(nodeMatcher.group(2)));
        if (view.getGraph2D().getNodeArray() != null) {
            Graph nodeGraph = null;
            for (Node node : view.getGraph2D().getNodeArray()) {
                String id;
                if (view.getGraph2D().getRealizer(node) instanceof y.view.hierarchy.ProxyAutoBoundsNodeRealizer) {
                    if (!tables.isEmpty())
                        ((Table) tables.getLast()).setColumns(items.<Item>toArray(new Item[0]));
                    items.clear();
                    groupNum++;
                    id = "n" + groupNum;
                    columnNum = -1;
                    appendTableGraph(tables, nodeMap.get(id), id);
                    nodeGraph = node.getGraph();
                } else {
                    if ("hide-table-column".equals(node.toString()))
                        continue;
                    columnNum++;
                    id = "n" + groupNum + "::n" + columnNum;
                    appendColumnGraph(items, nodeMap.get(id), id);
                }
                Object selection = graphNodes.get(index);
                if (selection instanceof List) {
                    List<column> columns = (List<column>) selection;
                    List<String> ids = new ArrayList<>();
                    if (columns != null)
                        for (column column : columns) {
                            ids.add(column.getId());
                            modelColumnIdMap.put(column.getId(), id);
                        }
                    listIdMap.put(id, ids.toArray(new String[0]));
                } else if (selection instanceof column) {
                    column column = (column) selection;
                    modelColumnIdMap.put(column.getId(), id);
                    listIdMap.put(id, new String[]{column.getId()});
                } else if (selection instanceof table) {
                    table table = (table) selection;
                    modelTableIdMap.put(table.getId(), id);
                    listIdMap.put(id, new String[]{table.getId()});
                } else if (selection instanceof process) {
                    process process = (process) selection;
                    modelTableIdMap.put(process.getId(), id);
                    listIdMap.put(id, new String[]{process.getId()});
                }
                index++;
                continue;
            }
        }
        if (!tables.isEmpty())
            ((Table) tables.getLast()).setColumns(items.<Item>toArray(new Item[0]));
        Pattern relationshipPattern = Pattern.compile("<edge\\s+id=\"(.+?)\".+?source=\"(.+?)\".+?target=\"(.+?)\">", 32);
        Matcher relationshipMatcher = relationshipPattern.matcher(xml);
        Map<String, String[]> relationshipMap = (Map) new HashMap<>();
        while (relationshipMatcher.find()) {
            relationshipMap.put(relationshipMatcher.group(1), new String[]{relationshipMatcher.group(2), relationshipMatcher.group(3)});
        }
        LinkedList<EdgeRelation> edges = new LinkedList<>();
        int edgeNum = 0;
        if (relationships != null) {
            Collections.reverse(relationships);
            Set<Pair<String, String>> fddRelations = new HashSet<>();
            Map<Pair<String, String>, String> relationshipEdgeMap = new HashMap<>();
            for (relationship relationship : relationships) {
                targetColumn target = relationship.getTarget();
                List<sourceColumn> sourceColumn = relationship.getSources();
                if (target != null && sourceColumn != null)
                    for (sourceColumn source : sourceColumn) {
                        String sourceId = getSourceId(source, modelColumnIdMap, modelTableIdMap);
                        String targetId = getTargetId(target, modelColumnIdMap, modelTableIdMap);
                        if (sourceId != null && targetId != null) {
                            Pair<String, String> relationshipPair = new Pair(sourceId, targetId);
                            if (!fddRelations.contains(relationshipPair)) {
                                if (RelationshipType.fdd.name().equals(relationship.getType()))
                                    fddRelations.add(relationshipPair);
                                if (relationshipEdgeMap.containsKey(relationshipPair)) {
                                    if (RelationshipType.fdd.name().equals(relationship.getType()))
                                        relationshipIdMap.put(relationshipEdgeMap.get(relationshipPair), new String[]{relationship.getId(), relationship.getType()});
                                    continue;
                                }
                                EdgeRelation edge = new EdgeRelation();
                                edge.setId("e" + edgeNum);
                                edge.setSourceId(sourceId);
                                edge.setTargetId(targetId);
                                edges.add(edge);
                                relationshipIdMap.put(edge.getId(), new String[]{relationship.getId(), relationship.getType()});
                                relationshipEdgeMap.put(relationshipPair, edge.getId());
                                edgeNum++;
                            }
                        }
                    }
            }
        }
        JSONObject graphObject = new JSONObject();
        graphObject.put("tables", tables.toArray(new Table[0]));
        graphObject.put("edges", edges.toArray(new EdgeRelation[0]));
        graph.put("elements", graphObject);
        graph.put("tooltip", tooltipMap);
        graph.put("listIdMap", listIdMap);
        graph.put("relationshipIdMap", relationshipIdMap);
        if (containGraphML)
            graph.put("graphML", graphML);
        return new Pair3(graph, dataflow1, graphContent.third);
    }


    private String getTargetId(targetColumn target, Map<String, String> modelColumnIdMap, Map<String, String> modelTableIdMap) {
        if (target.getId() != null) {
            String targetId = modelColumnIdMap.get(target.getId());
            if (targetId != null)
                return targetId;
            if (target.getTarget_id() != null)
                return modelTableIdMap.get(target.getTarget_id());
        }
        return null;
    }

    private Graph2DView initGraph(String graphml) throws IOException {
        Graph2DView view = new Graph2DView();
        view.setFitContentOnResize(true);
        new HierarchyManager((Graph) view.getGraph2D());
        BridgeCalculator bridgeCalculator = new BridgeCalculator();
        bridgeCalculator.setCrossingMode((short) 1);
        ((DefaultGraph2DRenderer) view.getGraph2DRenderer())
            .setBridgeCalculator(bridgeCalculator);
        view.getRenderingHints().put(YRenderingHints.KEY_GROUP_STATE_PAINTING,
            YRenderingHints.VALUE_GROUP_STATE_PAINTING_OFF);
        InputStream is = new ByteArrayInputStream(graphml.getBytes());
        GraphMLIOHandler graphMLIOHandler = new GraphMLIOHandler();
        view.getGraph2D().clear();
        graphMLIOHandler.read(view.getGraph2D(), is);
        is.close();
        return view;
    }

    private void appendColumnGraph(List<Item> items, Document document, String id) {
        Item item = new Item();
        item.setId(id);
        Element geometry = (Element) document.select("geometry").get(0);
        String width = geometry.attr("width");
        String height = geometry.attr("height");
        String x = geometry.attr("x");
        String y = geometry.attr("y");
        item.setX(Float.parseFloat(x));
        item.setY(Float.parseFloat(y));
        item.setWidth(Float.parseFloat(width));
        item.setHeight(Float.parseFloat(height));
        Element labelElement = (Element) document.select("NodeLabel").get(0);
        width = labelElement.attr("width");
        height = labelElement.attr("height");
        x = labelElement.attr("x");
        y = labelElement.attr("y");
        String content = labelElement.text();
        String fontFamily = labelElement.attr("fontFamily");
        String fontSize = labelElement.attr("fontSize");
        Label label = new Label();
        label.setX(Float.parseFloat(x));
        label.setY(Float.parseFloat(y));
        label.setWidth(Float.parseFloat(width));
        label.setHeight(Float.parseFloat(height));
        label.setFontFamily(fontFamily);
        label.setFontSize(fontSize);
        label.setContent(content);
        item.setLabel(label);
        items.add(item);
    }

    private String getSourceId(sourceColumn
                                   source, Map<String, String> modelColumnIdMap, Map<String, String> modelTableIdMap) {
        if (source.getId() != null) {
            String sourceId = modelColumnIdMap.get(source.getId());
            if (sourceId != null) {
                return sourceId;
            }
            if (source.getSource_id() != null) {
                return modelTableIdMap.get(source.getSource_id());
            }
        } else if (source.getSource_id() != null) {
            return modelTableIdMap.get(source.getSource_id());
        }
        return null;
    }

}
